/*
 * lws-minimal-secure-streams-policy2c
 *
 * Written in 2010-2021 by Andy Green <andy@warmcat.com>
 *
 * This file is made available under the Creative Commons CC0 1.0
 * Universal Public Domain Dedication.
 *
 *
 * This reads policy JSON on stdin and emits it as compileable
 * C structs.
 *
 * It's useful if your platform is too space-constrained for a
 * JSON policy and needs to build a static policy in C via
 * LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY... this way you can
 * still create and maintain the JSON policy but implement it directly
 * as C structs in your code.
 */

#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <assert.h>

static int interrupted, bad = 1;


static void
sigint_handler(int sig)
{
	interrupted = 1;
}

struct aggstr {
	struct aggstr *next;

	const char *orig;
	size_t offset;
};

static struct aggstr *rbomap,	/* retry / backoff object map */
		     *trustmap, /* trust store map */
		     *certmap;	/* x.509 cert map */
static size_t last_offset;



static const char *
purify_csymbol(const char *in, char *temp, size_t templen)
{
	const char *otemp = temp;

	assert (strlen(in) < templen);

	while (*in) {
		if ((*in >= 'a' && *in <= 'z') || (*in >= 'A' && *in <= 'Z') ||
		    (*in >= '0' && *in <= '9'))
			*temp++ = *in;
		else
			*temp++ = '_';

		in++;
	}

	*temp = '\0';

	return otemp;
}

int main(int argc, const char **argv)
{
	const lws_ss_policy_t *pol, *lastpol = NULL;
	struct lws_context_creation_info info;
	size_t json_size = 0, est = 0;
	struct lws_context *context;
	const lws_ss_auth_t *auth;
	char prev[128], curr[128];
	int unique_rbo = 0, m, n;
	char buf[64], buf1[64];
	lws_ss_metadata_t *md;
	struct aggstr *a, *a1;

	signal(SIGINT, sigint_handler);

	memset(&info, 0, sizeof info);
	lws_cmdline_option_handle_builtin(argc, argv, &info);

	lwsl_user("LWS secure streams policy2c [-d<verb>]\n");

	info.fd_limit_per_thread = 1 + 6 + 1;
	info.port = CONTEXT_PORT_NO_LISTEN;

	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
		       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;

	/* create the context */

	context = lws_create_context(&info);
	if (!context) {
		lwsl_err("lws init failed\n");
		return 1;
	}

	lws_ss_policy_parse_begin(context, 0);

	printf("/*\n * Autogenerated from the following JSON policy\n */\n\n#if 0\n");

	do {
		int m, n = (int)read(0, buf, sizeof(buf));

		if (n < 1)
			break;

		m = lws_ss_policy_parse(context, (uint8_t *)buf, (size_t)n);

		printf("%.*s", n, buf);
		json_size += (unsigned int)n;

		if (m < 0 && m != LEJP_CONTINUE) {
			lwsl_err("%s: policy parse failed... lws has WITH_ROLEs"
				 "for what's in the JSON?\n", __func__);
			goto bail;
		}
	} while (1);

	printf("\n\n Original JSON size: %zu\n#endif\n\n", json_size);

	lwsl_notice("%s: parsed JSON\n", __func__);

	/*
	 * Well, this is fun, isn't it... we have parsed the JSON into in-memory
	 * policy objects, and it has set the context policy pointer to the head
	 * of those but has not set the new policy (which would free the x.509).
	 *
	 * We want to walk the streamtype list first discovering unique objects
	 * and strings referenced there and emitting them compactly as C data,
	 * and then second to emit the streamtype linked-list referring to those
	 * objects.
	 *
	 * For const strings, we aggregate them and avoid generating extra
	 * pointers by encoding the reference as &_lws_ss_staticpol_str[xxx]
	 * where xxx is the fixed offset in the aggregated monster-string.  When
	 * doing that, we keep a map of original pointers to offsets.
	 *
	 * Although we want to minimize memory used by the emitted C, we don't
	 * have to sweat memory during this conversion since it's happening on a
	 * PC
	 */

	pol = lws_ss_policy_get(context);

	while (pol) {

		/*
		 * Walk the metadata list gathering strings and issuing the
		 * C struct
		 */

		md = pol->metadata;

		if (md) {
			int idx = 0;

			printf("\nstatic const lws_ss_metadata_t ");

			prev[0] = '\0';
			md = pol->metadata;
			while (md) {

				est += sizeof(lws_ss_metadata_t);

				lws_snprintf(curr, sizeof(curr), "_md_%s_%s",
					purify_csymbol(pol->streamtype, buf,
						       sizeof(buf)),
					purify_csymbol(md->name, buf1,
						       sizeof(buf1)));

				printf("%s = {\n", curr);
				if (prev[0])
					printf("\t.next = (void *)&%s, \n", prev);

				printf("\t.name = \"%s\",\n", (const char *)md->name);
				if (md->value__may_own_heap) {
					printf("\t.value__may_own_heap = (void *)\"%s\",\n",
							(const char *)md->value__may_own_heap);
					printf("\t.value_length = 0x%x,\n",
						(unsigned int)strlen(
							(const char *)md->value__may_own_heap));
				}

				printf("\t.length = %d,\n", idx++); // md->length);
				printf("\t.value_is_http_token = 0x%x,\n",
					(unsigned int)md->value_is_http_token);
				printf("}");
				if (md->next)
					printf(",\n");

				lws_strncpy(prev, curr, sizeof(prev));

				md = md->next;
			}

			printf(";\n\n");
		}

		/*
		 * Create unique retry policies... have we seen this guy?
		 */

		if (pol->retry_bo) {
			a = rbomap;
			while (a) {
				if (a->orig == (const char *)pol->retry_bo)
					break;

				a = a->next;
			}

			if (!a) {

				/* We haven't seen it before and need to create it */

				a = malloc(sizeof(*a));
				if (!a)
					goto bail;
				a->next = rbomap;
				a->offset = (unsigned int)unique_rbo++;
				a->orig = (const char *)pol->retry_bo;
				rbomap = a;

				printf("static const uint32_t _rbo_bo_%zu[] = {\n",
					a->offset);
				for (n = 0; n < pol->retry_bo->retry_ms_table_count; n++)
					printf(" %u, ", (unsigned int)
					       pol->retry_bo->retry_ms_table[n]);

				est += sizeof(uint32_t) *
					pol->retry_bo->retry_ms_table_count;

				printf("\n};\nstatic const "
				       "lws_retry_bo_t _rbo_%zu = {\n", a->offset);

				printf("\t.retry_ms_table = _rbo_bo_%zu,\n",
					a->offset);
				printf("\t.retry_ms_table_count = %u,\n",
					pol->retry_bo->retry_ms_table_count);
				printf("\t.conceal_count = %u,\n",
					pol->retry_bo->conceal_count);
				printf("\t.secs_since_valid_ping = %u,\n",
					pol->retry_bo->secs_since_valid_ping);
				printf("\t.secs_since_valid_hangup = %u,\n",
					pol->retry_bo->secs_since_valid_hangup);
				printf("\t.jitter_percent = %u,\n",
					pol->retry_bo->jitter_percent);
				printf("};\n");

				est += sizeof(lws_retry_bo_t);
			}
		}

		/*
		 * How about his trust store, it's new to us?
		 */

		if (pol->trust.store) {
			a = trustmap;
			while (a) {
				if (a->orig == (const char *)pol->trust.store)
					break;

				a = a->next;
			}

			if (!a) {

				/* it's new to us... */

				a = malloc(sizeof(*a));
				if (!a)
					goto bail;
				a->next = trustmap;
				a->offset = 0; /* don't care, just track seen */
				a->orig = (const char *)pol->trust.store;
				trustmap = a;

				/*
				 * Have a look through his x.509 stack...
				 * any that're new to us?
				 */

				for (n = 0; n < pol->trust.store->count; n++) {
					if (!pol->trust.store->ssx509[n])
						continue;
					a1 = certmap;
					while (a1) {
						if (a1->orig == (const char *)pol->trust.store->ssx509[n])
							break;
						a1 = a1->next;
					}

					if (!a1) {
						/*
						 * This x.509 cert is new to us...
						 * let's capture the DER
						 */

						a1 = malloc(sizeof(*a1));
						if (!a1)
							goto bail;
						a1->next = certmap;
						a1->offset = 0; /* don't care, just track seen */
						a1->orig = (const char *)pol->trust.store->ssx509[n];
						certmap = a1;

						printf("static const uint8_t _ss_der_%s[] = {\n",
							purify_csymbol(pol->trust.store->ssx509[n]->vhost_name,
									buf, sizeof(buf)));

						for (m = 0; m < (int)pol->trust.store->ssx509[n]->ca_der_len; m++) {
							if ((m & 7) == 0)
								printf("\t/* 0x%3x */ ", m);

							printf("0x%02X, ", pol->trust.store->ssx509[n]->ca_der[m]);
							if ((m & 7) == 7)
								printf("\n");
						}

						printf("\n};\nstatic const lws_ss_x509_t _ss_x509_%s = {\n",
								purify_csymbol(pol->trust.store->ssx509[n]->vhost_name,
								buf, sizeof(buf)));
						printf("\t.vhost_name = \"%s\",\n", pol->trust.store->ssx509[n]->vhost_name);
						printf("\t.ca_der = _ss_der_%s,\n",
							purify_csymbol(pol->trust.store->ssx509[n]->vhost_name,
								buf, sizeof(buf)));
						printf("\t.ca_der_len = %zu,\n", pol->trust.store->ssx509[n]->ca_der_len);
						printf("};\n");

						est += sizeof(lws_ss_x509_t) + pol->trust.store->ssx509[n]->ca_der_len;
					}

				}


				printf("static const lws_ss_trust_store_t _ss_ts_%s = {\n",
					purify_csymbol(pol->trust.store->name,
							buf, sizeof(buf)));

				printf("\t.name = \"%s\",\n", pol->trust.store->name);
				printf("\t.count = %d,\n", pol->trust.store->count);
				printf("\t.ssx509 = {\n");

				for (n = pol->trust.store->count - 1; n >= 0 ; n--)
					printf("\t\t&_ss_x509_%s,\n",
						pol->trust.store->ssx509[n]->vhost_name);

				printf("\t}\n};\n");

				est += sizeof(lws_ss_trust_store_t);

			}
		}

		pol = pol->next;
	}


	/* dump any streamtype's http resp map */

	pol = lws_ss_policy_get(context);
	m = 0;

	while (pol) {

		lws_snprintf(curr, sizeof(curr), "_ssp_%s",
			purify_csymbol(pol->streamtype, buf, sizeof(buf)));

		/* if relevant, dump http resp map */

		switch (pol->protocol) {
		case LWSSSP_H1:
		case LWSSSP_H2:
		case LWSSSP_WS:

			if (!pol->u.http.count_respmap)
				break;

			if (!m)
				printf("\nstatic const lws_ss_http_respmap_t ");
			else
				printf(",\n");
			m++;

			printf("%s_http_respmap[] = {\n", curr);
			for (n = 0; n < pol->u.http.count_respmap; n++) {
				printf("\t{ %d, 0x%x },\n",
						pol->u.http.respmap[n].resp,
						pol->u.http.respmap[n].state);

				est += sizeof(lws_ss_http_respmap_t);
			}
			printf("}");
			break;
		}

		pol = pol->next;
	}

	if (m)
		printf(";\n");

	/*
	 * The auth map
	 */

	auth = lws_ss_auth_get(context);
	if (auth)
		printf("\nstatic const lws_ss_auth_t ");
	prev[0] = '\0';

	while (auth) {
		lws_snprintf(curr, sizeof(curr), "_ssau_%s",
			purify_csymbol(auth->name, buf, sizeof(buf)));

		printf("%s = {\n", curr);
		if (prev[0])
			printf("\t.next = (void *)&%s,\n", prev);

		printf("\t.name = \"%s\",\n", auth->name);
		printf("\t.type= \"%s\",\n", auth->type);
		printf("\t.streamtype = \"%s\",\n", auth->streamtype);
		printf("\t.blob_index = %d,\n", auth->blob_index);
		printf("}");
		if (auth->next)
			printf(",");
		else
			printf(";");
		printf("\n");

		lws_strncpy(prev, curr, sizeof(prev));

		auth = auth->next;
	}

	if (lws_ss_auth_get(context))
		printf("\n");

	/*
	 * The streamtypes
	 */

	pol = lws_ss_policy_get(context);

	printf("\nstatic const lws_ss_policy_t ");
	prev[0] = '\0';

	while (pol) {

		est += sizeof(*pol);

		lws_snprintf(curr, sizeof(curr), "_ssp_%s",
			purify_csymbol(pol->streamtype, buf, sizeof(buf)));

		printf("%s = {\n", curr);

		if (prev[0])
			printf("\t.next = (void *)&%s,\n", prev);

		printf("\t.streamtype = \"%s\",\n", pol->streamtype);
		if (pol->endpoint)
			printf("\t.endpoint = \"%s\",\n", pol->endpoint);
		if (pol->rideshare_streamtype)
			printf("\t.rideshare_streamtype = \"%s\",\n",
				pol->rideshare_streamtype);
		if (pol->payload_fmt)
			printf("\t.payload_fmt = \"%s\",\n",
				pol->payload_fmt);
		if (pol->socks5_proxy)
			printf("\t.socks5_proxy = \"%s\",\n",
				pol->socks5_proxy);

		if (pol->auth)
			printf("\t.auth = &_ssau_%s,\n",
			       purify_csymbol(pol->auth->name, buf, sizeof(buf)));

		{
			lws_ss_metadata_t *nv = pol->metadata, *last = NULL;

			while (nv) {
				last = nv;
				nv = nv->next;
			}
			if (pol->metadata)
				printf("\t.metadata = (void *)&_md_%s_%s,\n",
					purify_csymbol(pol->streamtype, buf, sizeof(buf)),
					purify_csymbol(last->name, buf1, sizeof(buf1)));
		}


		switch (pol->protocol) {
		case LWSSSP_H1:
		case LWSSSP_H2:
		case LWSSSP_WS:

			printf("\t.u = {\n\t\t.http = {\n");

			if (pol->u.http.method)
				printf("\t\t\t.method = \"%s\",\n",
					pol->u.http.method);
			if (pol->u.http.url)
				printf("\t\t\t.url = \"%s\",\n",
					pol->u.http.url);
			if (pol->u.http.multipart_name)
				printf("\t\t\t.multipart_name = \"%s\",\n",
					pol->u.http.multipart_name);
			if (pol->u.http.multipart_filename)
				printf("\t\t\t.multipart_filename = \"%s\",\n",
					pol->u.http.multipart_filename);
			if (pol->u.http.multipart_content_type)
				printf("\t\t\t.multipart_content_type = \"%s\",\n",
					pol->u.http.multipart_content_type);
			if (pol->u.http.auth_preamble)
				printf("\t\t\t.auth_preamble = \"%s\",\n",
					pol->u.http.auth_preamble);

			if (pol->u.http.respmap) {
				printf("\t\t\t.respmap = (void *)&%s_http_respmap,\n",
						curr);
				printf("\t\t\t.count_respmap = %d,\n",
						pol->u.http.count_respmap);
			}

			if (pol->u.http.blob_header[0]) {
				printf("\t\t\t.blob_header = {\n");
				for (n = 0; n < (int)LWS_ARRAY_SIZE(pol->u.http.blob_header); n++)
					if (pol->u.http.blob_header[n])
						printf("\t\t\t\t\"%s\",\n",
							pol->u.http.blob_header[n]);

				printf("\t\t\t},\n");
			}

			if (pol->protocol == LWSSSP_WS) {
				printf("\t\t\t.u = {\n\t\t\t\t.ws = {\n");
				if (pol->u.http.u.ws.subprotocol)
					printf("\t\t\t\t\t.subprotocol = \"%s\",\n",
						pol->u.http.u.ws.subprotocol);
				printf("\t\t\t\t\t.binary = %u\n", pol->u.http.u.ws.binary);
				printf("\t\t\t\t}\n\t\t\t},\n");
			}

			if (pol->u.http.resp_expect)
				printf("\t\t\t.resp_expect = %u,\n", pol->u.http.resp_expect);
			if (pol->u.http.fail_redirect)
				printf("\t\t\t.fail_redirect = %u,\n", pol->u.http.fail_redirect);

			printf("\t\t}\n\t},\n");

			break;
		case LWSSSP_MQTT:

			printf("\t.u = {\n\t\t.mqtt = {\n");

			if (pol->u.mqtt.topic)
				printf("\t\t\t.topic = \"%s\",\n",
					pol->u.mqtt.topic);
			if (pol->u.mqtt.subscribe)
				printf("\t\t\t.subscribe = \"%s\",\n",
					pol->u.mqtt.subscribe);
			if (pol->u.mqtt.will_topic)
				printf("\t\t\t.will_topic = \"%s\",\n",
					pol->u.mqtt.will_topic);
			if (pol->u.mqtt.will_message)
				printf("\t\t\t.will_message = \"%s\",\n",
					pol->u.mqtt.will_message);

			if (pol->u.mqtt.keep_alive)
				printf("\t\t\t.keep_alive = %u,\n",
					pol->u.mqtt.keep_alive);
			if (pol->u.mqtt.qos)
				printf("\t\t\t.qos = %u,\n",
					pol->u.mqtt.qos);
			if (pol->u.mqtt.clean_start)
				printf("\t\t\t.clean_start = %u,\n",
					pol->u.mqtt.clean_start);
			if (pol->u.mqtt.will_qos)
				printf("\t\t\t.will_qos = %u,\n",
					pol->u.mqtt.will_qos);
			if (pol->u.mqtt.will_retain)
				printf("\t\t\t.will_retain = %u,\n",
					pol->u.mqtt.will_retain);

			printf("\t\t}\n\t},\n");

			break;
		default:
			lwsl_err("%s: unknown ss protocol index %d\n", __func__,
					pol->protocol);
			goto bail;
		}

#if 0
		const lws_ss_trust_store_t *trust_store; /**< CA certs needed for conn
		       validation, only set between policy parsing and vhost creation */
#endif

		if (pol->retry_bo) {
			a = rbomap;
			while (a) {
				if (a->orig == (const char *)pol->retry_bo)
					break;

				a = a->next;
			}
			if (!a)
				goto bail;

			printf("\t.retry_bo = &_rbo_%zu,\n", a->offset);
		}

		if (pol->timeout_ms)
			printf("\t.timeout_ms = %u,\n", pol->timeout_ms);
		if (pol->flags)
			printf("\t.flags = 0x%x,\n", pol->flags);
		if (pol->flags)
			printf("\t.priority = 0x%x,\n", (unsigned int)pol->priority);
		if (pol->port)
			printf("\t.port = %u,\n", pol->port);
		if (pol->metadata_count)
			printf("\t.metadata_count = %u,\n", pol->metadata_count);
		printf("\t.protocol = %u,\n", pol->protocol);
		if (pol->client_cert)
			printf("\t.client_cert = %u,\n", pol->client_cert);

		if (pol->trust.store)
			printf("\t.trust = {.store = &_ss_ts_%s},\n",
				purify_csymbol(pol->trust.store->name,
							buf, sizeof(buf)));
#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4)
		if (pol->aws_region)
			printf("\t.aws_region= \"%s\",\n", pol->aws_region);
		if (pol->aws_service)
			printf("\t.aws_service= \"%s\",\n", pol->aws_service);

#endif


		printf("}");
		if (pol->next)
			printf(",\n");

		lws_strncpy(prev, curr, sizeof(prev));

		lastpol = pol;

		pol = pol->next;
	}

	printf(";\n");
	if (lastpol)
		printf("#define _ss_static_policy_entry _ssp_%s\n",
			purify_csymbol(lastpol->streamtype, buf, sizeof(buf)));

	est += last_offset;

	printf("/* estimated footprint %zu (when sizeof void * = %zu) */\n",
			est, sizeof(void *));

	lws_ss_policy_parse_abandon(context);
	bad = 0;

bail:


	lws_context_destroy(context);

	lwsl_user("Completed: %s\n", bad ? "failed" : "OK");

	return bad;
}
